package evl

import (
	"testing"

	"gitcode.com/deyiyangyang/bampoo/ast"
	"gitcode.com/deyiyangyang/bampoo/obj"
)

// TestEvalLogicalAnd tests the (and) logical operation
func TestEvalLogicalAnd(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	tests := []struct {
		name     string
		expr     *ast.LogicalExpr
		expected bool
	}{{
		name: "and with true and true",
		expr: &ast.LogicalExpr{
			Operator: "and",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: true},
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: true,
	}, {
		name: "and with true and false",
		expr: &ast.LogicalExpr{
			Operator: "and",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: true},
				&ast.BooleanLiteral{Value: false},
			},
		},
		expected: false,
	}, {
		name: "and with false and true",
		expr: &ast.LogicalExpr{
			Operator: "and",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: false},
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: false,
	}, {
		name: "and with false and false",
		expr: &ast.LogicalExpr{
			Operator: "and",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: false},
				&ast.BooleanLiteral{Value: false},
			},
		},
		expected: false,
	}, {
		name: "and with multiple operands",
		expr: &ast.LogicalExpr{
			Operator: "and",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: true},
				&ast.BooleanLiteral{Value: true},
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: true,
	}, {
		name: "and with multiple operands including false",
		expr: &ast.LogicalExpr{
			Operator: "and",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: true},
				&ast.BooleanLiteral{Value: false},
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: false,
	}, {
		name: "and with no operands (should return true)",
		expr: &ast.LogicalExpr{
			Operator: "and",
			Operands: []ast.Expression{},
		},
		expected: true,
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := evaluator.Eval(tt.expr)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tt.expected {
				t.Errorf("expected %v, got %v", tt.expected, boolVal.Value)
			}
		})
	}
}

// TestEvalLogicalOr tests the (or) logical operation
func TestEvalLogicalOr(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	tests := []struct {
		name     string
		expr     *ast.LogicalExpr
		expected bool
	}{{
		name: "or with true and true",
		expr: &ast.LogicalExpr{
			Operator: "or",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: true},
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: true,
	}, {
		name: "or with true and false",
		expr: &ast.LogicalExpr{
			Operator: "or",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: true},
				&ast.BooleanLiteral{Value: false},
			},
		},
		expected: true,
	}, {
		name: "or with false and true",
		expr: &ast.LogicalExpr{
			Operator: "or",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: false},
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: true,
	}, {
		name: "or with false and false",
		expr: &ast.LogicalExpr{
			Operator: "or",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: false},
				&ast.BooleanLiteral{Value: false},
			},
		},
		expected: false,
	}, {
		name: "or with multiple operands including true",
		expr: &ast.LogicalExpr{
			Operator: "or",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: false},
				&ast.BooleanLiteral{Value: true},
				&ast.BooleanLiteral{Value: false},
			},
		},
		expected: true,
	}, {
		name: "or with no operands (should return false)",
		expr: &ast.LogicalExpr{
			Operator: "or",
			Operands: []ast.Expression{},
		},
		expected: false,
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := evaluator.Eval(tt.expr)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tt.expected {
				t.Errorf("expected %v, got %v", tt.expected, boolVal.Value)
			}
		})
	}
}

// TestEvalLogicalNot tests the (not) logical operation
func TestEvalLogicalNot(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	tests := []struct {
		name     string
		expr     *ast.LogicalExpr
		expected bool
	}{{
		name: "not true",
		expr: &ast.LogicalExpr{
			Operator: "not",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: true},
			},
		},
		expected: false,
	}, {
		name: "not false",
		expr: &ast.LogicalExpr{
			Operator: "not",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: false},
			},
		},
		expected: true,
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := evaluator.Eval(tt.expr)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tt.expected {
				t.Errorf("expected %v, got %v", tt.expected, boolVal.Value)
			}
		})
	}
}

// checkErrorMessage checks if an error message contains a specific substring
func checkErrorMessage(s, substr string) bool {
	return len(s) >= len(substr) && s[len(s)-len(substr):] == substr
}

// TestEvalLogicalNotWithWrongArgs tests (not) with wrong number of arguments
func TestEvalLogicalNotWithWrongArgs(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	expr := &ast.LogicalExpr{
		Operator: "not",
		Operands: []ast.Expression{
			&ast.BooleanLiteral{Value: true},
			&ast.BooleanLiteral{Value: false},
		},
	}

	_, err := evaluator.Eval(expr)
	if err == nil {
		t.Fatal("expected error for not with multiple operands")
	}

	if !checkErrorMessage(err.Error(), "not requires exactly one operand") {
		t.Errorf("expected error message to contain 'not requires exactly one operand', got '%s'", err.Error())
	}
}

// TestEvalComplexBooleanExpressions tests complex nested boolean expressions
func TestEvalComplexBooleanExpressions(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	tests := []struct {
		name     string
		expr     ast.Expression
		expected bool
	}{{
		name: "and with nested or expressions",
		expr: &ast.LogicalExpr{
			Operator: "and",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: true},
				&ast.LogicalExpr{
					Operator: "or",
					Operands: []ast.Expression{
						&ast.BooleanLiteral{Value: false},
						&ast.BooleanLiteral{Value: true},
					},
				},
			},
		},
		expected: true,
	}, {
		name: "or with nested and expressions",
		expr: &ast.LogicalExpr{
			Operator: "or",
			Operands: []ast.Expression{
				&ast.BooleanLiteral{Value: false},
				&ast.LogicalExpr{
					Operator: "and",
					Operands: []ast.Expression{
						&ast.BooleanLiteral{Value: true},
						&ast.BooleanLiteral{Value: true},
					},
				},
			},
		},
		expected: true,
	}, {
		name: "not with nested and expression",
		expr: &ast.LogicalExpr{
			Operator: "not",
			Operands: []ast.Expression{
				&ast.LogicalExpr{
					Operator: "and",
					Operands: []ast.Expression{
						&ast.BooleanLiteral{Value: true},
						&ast.BooleanLiteral{Value: false},
					},
				},
			},
		},
		expected: true,
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := evaluator.Eval(tt.expr)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			boolVal, ok := result.(*obj.BooleanValue)
			if !ok {
				t.Fatalf("expected BooleanValue, got %T", result)
			}

			if boolVal.Value != tt.expected {
				t.Errorf("expected %v, got %v", tt.expected, boolVal.Value)
			}
		})
	}
}

// TestEvalBooleanTruthiness tests that boolean values are correctly evaluated for truthiness
func TestEvalBooleanTruthiness(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	tests := []struct {
		name     string
		condition ast.Expression
		expectedBranch string
	}{{
		name: "if with true condition",
		condition: &ast.BooleanLiteral{Value: true},
		expectedBranch: "consequence",
	}, {
		name: "if with false condition",
		condition: &ast.BooleanLiteral{Value: false},
		expectedBranch: "alternative",
	}}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create if expression with "consequence" and "alternative" branches
			ifExpr := &ast.IfExpr{
				Condition:   tt.condition,
				Consequence: &ast.StringLiteral{Value: "consequence"},
				Alternative: &ast.StringLiteral{Value: "alternative"},
			}

			result, err := evaluator.Eval(ifExpr)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}

			strVal, ok := result.(*obj.StringValue)
			if !ok {
				t.Fatalf("expected StringValue, got %T", result)
			}

			if strVal.Value != tt.expectedBranch {
				t.Errorf("expected %s branch to be taken, got %s", tt.expectedBranch, strVal.Value)
			}
		})
	}
}